#include <stdio.h>
#include <string.h>
#include <iostream>
#include <sstream>
#include <algorithm>
#include <iterator>
#include <vector>
#include <list>
#include <cstdlib>
#include "CommandParser.h"
#include "SharedFile.h"
#include "LogFile.h"
#include "Process.h"
#include "FileSharingProcess.h"
#include "FileDownloadRequestReceptorProcess.h"
#include "FileDownloadRequestSenderProcess.h"
#include "SignalHandler.h"
#include "SIGUSR1_Handler.h"
#include "SIGUSR2_Handler.h"

#define MAINPROCESSNAME "Main"

using namespace std;

void printHelp() {
	cout << "Comandos disponibles:" << endl;
	cout << "  - share         Agregar o eliminar archivo/s compartido/s." << endl;
	cout << "      Uso:        share [-e] rutaArchivo .. [rutaArchivoN]" << endl;
	cout << "      Parametros: rutaArchivo obligatorio. Ruta absoluta al archivo que se quiere compartir." << endl;
	cout << "                  -e opcional. Excluye el archivo indicado de los archivos compartidos." << endl;
	cout << "  - list          Consultar archivos compartidos por todos los usuarios." << endl;
	cout << "      Uso:        list [-m]" << endl;
	cout << "      Parametros: -m opcional. Lista solo los archivos compartidos por el usuario actual." << endl;
	cout << "  - download      Descargar el/los archivo/s indicado/s. Los archivos que no esten compartidos se ignoran." << endl;
	cout << "      Uso:        download rutaArchivo .. [rutaArchivoN]" << endl;
	cout << "      Parametros: rutaArchivo obligatorio. Archivo que se desea descargar (debe estar siendo compartido)." << endl;
	cout << "  - exit          Salir del programa y dejar de compatir los archivos." << endl;
	cout << "      Uso: exit" << endl;
}

bool fileExists(string filePath) {
	FILE* fp = fopen(filePath.c_str(), "r");
	if (fp) {
	    fclose(fp);
	    return true;
	} else {
	    return false;
	}
}

void handleShareCommand(LogFile* logFile, FileSharingProcess* fileSharing, list<string> parameters) {
	bool invalidPath = false;
	bool hasExcludeOption = (parameters.front() == "-e");

	logFile->log("[" + string(MAINPROCESSNAME) + "] Iniciando comando share.");

	if (hasExcludeOption) {
		parameters.pop_front();
	}

	logFile->log("[" + string(MAINPROCESSNAME) + "] Verificando archivos a compartir.");
	list<string>::iterator it = parameters.begin();
	while (it != parameters.end()) {
		if (!fileExists(*it)) {
			cout << "El archivo " << *it << " que se intenta compartir no existe. Vuelva a escribir el comando quitando este archivo." << endl;
			invalidPath = true;
		}

		++it;
	}

	if (!invalidPath) {
		fileSharing->setFiles(parameters);
		fileSharing->setIsExclusion(hasExcludeOption);
		fileSharing->start();
	} else {
		logFile->log("[" + string(MAINPROCESSNAME) + "] Algunos de los archivos ingresados para compartir no existen. Se cancela la operacion.");
	}
}

void handleListCommand(LogFile* logFile, list<string> parameters, string currentUserIdentifier) {
	SharedFile file;
	SharedRecord record;
	bool hasMineOption = ((parameters.size() > 0) && (parameters.front() == "-m"));
	bool availableSharedFiles = false;

	logFile->log("[" + string(MAINPROCESSNAME) + "] Iniciando comando list.");

	logFile->log("[" + string(MAINPROCESSNAME) + "] Bloqueando archivo compartido para lectura.");
	file.getReadLock();

	logFile->log("[" + string(MAINPROCESSNAME) + "] Recorriendo archivo compartido.");
	while (!file.isEOF()) {
		logFile->log("[" + string(MAINPROCESSNAME) + "] Obteniendo proximo registro (file: " + record.sharedFilePath + " - fifo: " + record.requestFifo + ")");
		record = file.getNextRecord();
		string fifoPath = record.requestFifo;
		string filePath = record.sharedFilePath;
		if ((fifoPath != "") && (filePath != "")) {
			if (hasMineOption) {
				if (fifoPath == currentUserIdentifier) {
					availableSharedFiles = true;
					cout << "file: " << record.sharedFilePath << " - fifo: " << record.requestFifo << endl;
				}
			} else {
				availableSharedFiles = true;
				cout << "file: " << record.sharedFilePath << " - fifo: " << record.requestFifo << endl;
			}
		}
	}

	if (!availableSharedFiles) {
		logFile->log("[" + string(MAINPROCESSNAME) + "] No se encontraron archivos compartidos.");
		cout << "No se encontraron archivos compartidos." << endl;
	}

	logFile->log("[" + string(MAINPROCESSNAME) + "] Desbloqueando archivo compartido.");
	file.unlock();
}

void handleDownloadCommand(LogFile* logFile, FileDownloadRequestSenderProcess* fileDownloadRequestSenderProcess, list<string> parameters) {
	logFile->log("[" + string(MAINPROCESSNAME) + "] Iniciando comando download.");

	fileDownloadRequestSenderProcess->setFiles(parameters);
	fileDownloadRequestSenderProcess->start();
}

int main() {
	// Instancia compartida del log para todos los procesos.
	LogFile logFile;
	logFile.log("[" + string(MAINPROCESSNAME) + "] Nueva instancia de ConcuShare iniciada.");

	// Registro los handlers para las señales:
	//    SIGUSR1: cuando se termina de actualizar el archivo compartido.
	//    SIGUSR2: cuando se termina de descargar un archivo.
	SIGUSR1_Handler sigusr1_handler;
	SIGUSR2_Handler sigusr2_handler;
	SignalHandler::getInstance()->registrarHandler(SIGUSR1, &sigusr1_handler);
	SignalHandler::getInstance()->registrarHandler(SIGUSR2, &sigusr2_handler);

	// Genero un nombre unico para el fifo de recepcion de pedidos.
	stringstream stream;
	stream << REQUESTRECEPTORFIFOBASENAME << getpid();
	string requestReceptorFifoName = stream.str();

	// Genero un nombre unico para la carpeta de archivos descargados.
	stream.str("");
	stream << ROOTDIRECTORY << DOWNLOADDIRECTORYBASENAME << getpid();
	string donwloadDirectory = stream.str();

	// Creo una instancia del proceso de recepcion de pedidos de descarga de archivos y lo inicio.
	// Este proceso entra en un loop hasta recibir una señal que lo interrumpe.
	FileDownloadRequestReceptorProcess fileDownloadRequestReceptorProcess(&logFile);
	fileDownloadRequestReceptorProcess.setRequestFifoName(requestReceptorFifoName);
	fileDownloadRequestReceptorProcess.setParentProcessName(MAINPROCESSNAME);
	fileDownloadRequestReceptorProcess.start();

	// Creo una instancia del proceso de comparticion de archivos.
	// Este proceso se ejecutara tantas veces como el usuario desee modificar sus archivos compartidos.
	FileSharingProcess fileSharingProcess(&logFile);
	fileSharingProcess.setRequestFifoName(requestReceptorFifoName);
	fileSharingProcess.setParentProcessName(MAINPROCESSNAME);

	// Creo una instancia del proceso de envio de pedidos de descarga de archivos.
	// Este proceso se ejecutara tantas veces como el usuario desee descargar archivos compartidos.
	FileDownloadRequestSenderProcess fileDownloadRequestSenderProcess(&logFile);
	fileDownloadRequestSenderProcess.setDownloadDirectory(donwloadDirectory);
	fileDownloadRequestSenderProcess.setParentProcessName(MAINPROCESSNAME);

	cout << "---------------------------" << endl;
	cout << "* Bienvenido a ConcuShare *" << endl;
	cout << "---------------------------" << endl;
	cout << "Por favor, ingrese la operacion a realizar." << endl;
	cout << "Ingrese 'help' para ver los comandos disponibles." << endl;

	string commandString = "";
	CommandParser parser;

	// Loop de recepcion y parseo de comandos del usuario.
	while (parser.getCommand() != EXIT) {
		cout << "> ";
		getline(cin, commandString);

		logFile.log("[" + string(MAINPROCESSNAME) + "] Comando tipeado por el usuario: " + commandString);
		if (parser.parse(commandString)) {
			logFile.log("[" + string(MAINPROCESSNAME) + "] Comando parseado: " + parser.getCommandString());

			switch (parser.getCommand()) {
				case EXIT: break;
				case SHARE:
					handleShareCommand(&logFile, &fileSharingProcess, parser.getParameters());
					break;
				case LIST:
					handleListCommand(&logFile, parser.getParameters(), ROOTDIRECTORY + requestReceptorFifoName);
					break;
				case DOWNLOAD:
					handleDownloadCommand(&logFile, &fileDownloadRequestSenderProcess, parser.getParameters());
					break;
				case HELP:
					printHelp();
					break;

				default: printHelp();
			}
		} else {
			logFile.log("[" + string(MAINPROCESSNAME) + "] No se pudo parsear el commando ingresado por el usuario: " + commandString);
			printHelp();
		}
	}

	logFile.log("[" + string(MAINPROCESSNAME) + "] Cerrando ConcuShare.");
	cout << "Cerrando ConcuShare..." << endl;

	// Limpiar los archivos compartidos.
	logFile.log("[" + string(MAINPROCESSNAME) + "] Limpiando archivos compartidos.");
	cout << "Limpiando archivos compartidos..." << endl;
	fileSharingProcess.setCleanAll(true);
	fileSharingProcess.start();
	fileSharingProcess.wait();

	// Enviar señal para finalizar la recepcion de pedidos de descarga de archivos.
	logFile.log("[" + string(MAINPROCESSNAME) + "] Terminando el proceso de recepcion de pedidos de descarga.");
	cout << "Terminando el proceso de recepcion de pedidos de descarga..." << endl;
	list<int> processIds = fileDownloadRequestReceptorProcess.getProcessIds();
	list<int>::iterator it;
	int processId = 0;
	for (it = processIds.begin(); it != processIds.end(); it++) {
		processId = *it;
		kill(processId, SIGINT);
	}

	// Esperar a que el proceso de de pedidos de descarga de archivos termine.
	logFile.log("[" + string(MAINPROCESSNAME) + "] Esperando la finalizacion del proceso de recepcion de pedidos de descarga.");
	cout << "Esperando la finalizacion del proceso de recepcion de pedidos de descarga..." << endl;
	fileDownloadRequestReceptorProcess.wait();

	// Esperar a que finalicen las descargas de archivos activas.
	logFile.log("[" + string(MAINPROCESSNAME) + "] Esperando la finalizacion de las descargas pendientes.");
	cout << "Esperando la finalizacion de las descargas pendientes..." << endl;
	fileDownloadRequestSenderProcess.wait();

	// Destruir handler de señales.
	logFile.log("[" + string(MAINPROCESSNAME) + "] Destruyendo handler de señales.");
	SignalHandler::destruir();

	logFile.log("[" + string(MAINPROCESSNAME) + "] ConcuShare cerrado con exito.");
	cout << "ConcuShare cerrado con exito." << endl;

	return 0;
}
